package com.xerland.tbootstrap.core.mqtt;

import android.content.Context;
import android.widget.Toast;

import com.xerland.tbootstrap.utils.SpfManager;

import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import timber.log.Timber;

/**
 * Fileame: com.xerland.tbootstrap.core.mqtt
 * Desription:
 * Class and methods:
 * <p>
 * Author: Anson Chan
 * Copyright: 2016 Xerland Co. Ltd. All rights reserved.
 * Contact:    anson_chan@xerland.com
 * Deffield    updated: Updated
 */
public class MqttHelper {
    MqttAndroidClient mqttAndroidClient;
    final String serverUriPrefix = "tcp://";

    public static final String clientIdPrefix = "/user/";


    private List<Map.Entry<String, TopicHandler>> topicHandlerPairList;
    private List<Map.Entry<String, String>> publishAfterSubscriptionList;
    private List<String> subscriptionList;
    private String serverUri;
    private String clientId;

    Context context;

    public MqttHelper(Context context) {
        this.context = context;
        topicHandlerPairList = new ArrayList<Map.Entry<String, TopicHandler>>();
        publishAfterSubscriptionList = new ArrayList<Map.Entry<String, String>>();
        subscriptionList = new ArrayList<String>();
        SpfManager spfManager = new SpfManager(context);
        serverUri = serverUriPrefix+spfManager.loadServerUrl()+":"+Integer.toString(spfManager.loadServerPort());
        clientId = clientIdPrefix+spfManager.loadUserId();
        doMqInit();
    }

    void doMqInit() {

        mqttAndroidClient = new MqttAndroidClient(context, serverUri, clientId);
        mqttAndroidClient.setCallback(new MqttCallbackExtended() {
            @Override
            public void connectComplete(boolean reconnect, String serverURI) {
                if (reconnect) {
                    Timber.e("Reconnected to : " + serverURI);
                    // Because Clean Session is true, we need to re-subscribe
//                    subscribeToTopic();
                } else {
                    Timber.e("Connected to: " + serverURI);
                }
            }

            @Override
            public void connectionLost(Throwable cause) {
                Timber.i("The Connection was lost.");
            }

            @Override
            public void messageArrived(String topic, MqttMessage message) throws Exception {
                String msgPayload = new String(message.getPayload());
//                Timber.e("Incoming message: " + msgPayload);
                for(Map.Entry<String, TopicHandler> topicHandlerPair:topicHandlerPairList){
                    if (topicHandlerPair.getKey().equals(topic)) {
                        topicHandlerPair.getValue().run(topic, msgPayload);
                    }
                }
            }

            @Override
            public void deliveryComplete(IMqttDeliveryToken token) {
            }
        });


    }

    public void connect() {
        MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
        mqttConnectOptions.setAutomaticReconnect(true);
        mqttConnectOptions.setCleanSession(false);

        try {
            //addToHistory("Connecting to " + serverUri);
            if (context == null) {
                Timber.e("Contex null!");
            }
            mqttAndroidClient.connect(mqttConnectOptions, context, new IMqttActionListener() {
                @Override
                public void onSuccess(IMqttToken asyncActionToken) {
                    DisconnectedBufferOptions disconnectedBufferOptions = new DisconnectedBufferOptions();
                    disconnectedBufferOptions.setBufferEnabled(true);
                    disconnectedBufferOptions.setBufferSize(100);
                    disconnectedBufferOptions.setPersistBuffer(false);
                    disconnectedBufferOptions.setDeleteOldestMessages(false);
                    mqttAndroidClient.setBufferOpts(disconnectedBufferOptions);
                    Timber.e("mqttAndroidClient connect success!");
                    subscribeToTopic();
                }

                @Override
                public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                    Toast.makeText(context, "Connection Failed!", Toast.LENGTH_SHORT).show();
                    Timber.e("Failed to connect to: " + serverUri);
                    Timber.e("Exception"+exception.getMessage());
                    exception.printStackTrace();
                }
            });


        } catch (MqttException ex){
            ex.printStackTrace();
        }
    }

    public void subscribeToTopic(){

        try {
            if (topicHandlerPairList.isEmpty()) {
                Timber.e("subscription list empty!");
                return;
            }
//            subscriptionList = new ArrayList<String>();
            for (Map.Entry<String, TopicHandler> topicHandlerEntry : topicHandlerPairList) {
                Timber.e("add subscription to list: " + topicHandlerEntry.getKey());
                subscriptionList.add(topicHandlerEntry.getKey());
            }
            for (String sub : subscriptionList) {
                Timber.e("do sub:%s", sub);
            }
            mqttAndroidClient.subscribe(subscriptionList.toArray(new String[subscriptionList.size()]), new int[subscriptionList.size()], null, new IMqttActionListener() {
                @Override
                public void onSuccess(IMqttToken asyncActionToken) {
                    Timber.e("Subscribed!");
                    for (Map.Entry<String, String> publishAfterSubscription : publishAfterSubscriptionList) {
                        Timber.e("do publish %s : %s", publishAfterSubscription.getKey(), publishAfterSubscription.getValue());
                        publishMessage(publishAfterSubscription.getKey(), publishAfterSubscription.getValue());
                    }
                }

                @Override
                public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                    Timber.e("Failed to subscribe");
                }
            });

            // THIS DOES NOT WORK!
//            mqttAndroidClient.subscribe(subscriptionTopic, 0, new IMqttMessageListener() {
//                @Override
//                public void messageArrived(String topic, MqttMessage message) throws Exception {
//                    // message Arrived!
//                    System.out.println("Message: " + topic + " : " + new String(message.getPayload()));
//                }
//            });

        } catch (MqttException ex){
            Timber.e("Exception whilst subscribing");
            ex.printStackTrace();
        }
    }

    public void publishMessage(String topic, String payload){
        try {
            if (!mqttAndroidClient.isConnected()) {
                return;
            }
            MqttMessage message = new MqttMessage();
            message.setPayload(payload.getBytes());
            mqttAndroidClient.publish(topic, message);
            Timber.d("Message Published");
            if(!mqttAndroidClient.isConnected()){
                Timber.i(mqttAndroidClient.getBufferedMessageCount() + " messages in buffer.");
            }
        } catch (MqttException e) {
            System.err.println("Error Publishing: " + e.getMessage());
            e.printStackTrace();
        }
    }

    public void setTopicHandler(String topic, TopicHandler topicHandler){
        for (Map.Entry<String, TopicHandler> topicHandlerEntry : topicHandlerPairList) {
            if (topicHandlerEntry.getKey().equals(topic)) {
                topicHandlerEntry.setValue(topicHandler);
                return;
            }
        }
        topicHandlerPairList.add(new AbstractMap.SimpleEntry(topic, topicHandler));
    }

    public void setPublishAfterSubscription(String topic, String payload) {
        publishAfterSubscriptionList.add(new AbstractMap.SimpleEntry(topic, payload));
    }

    public boolean isconected() {
        return mqttAndroidClient.isConnected();
    }

    public void close() {
        try {
            if (subscriptionList != null) {
                mqttAndroidClient.unsubscribe(subscriptionList.toArray(new String[subscriptionList.size()]));
            }

            if(mqttAndroidClient != null) {
                Timber.e("do mqtt Android client disconnect!");
                mqttAndroidClient.disconnect(); //1000

            }
//            mqttAndroidClient.unregisterResources();
        } catch (MqttException e) {
            Timber.e(e.getMessage());
        }
//        mqttAndroidClient.close();
    }

    public void release() {
//        mqttAndroidClient.unregisterResources();
        topicHandlerPairList.clear();
        publishAfterSubscriptionList.clear();
        subscriptionList.clear();
        topicHandlerPairList = null;
        publishAfterSubscriptionList = null;
        subscriptionList = null;
        mqttAndroidClient = null;
    }

    public interface TopicHandler{
        public void run(String topic, String payload);
    }

}
